{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Using batch prediction with Keras models on AI Platform\n",
    "\n",
    "## Functional API example"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "**Use a TensorFlow Enterprise 2.3 instance to run this notebook.**"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Define environment variables and create a storage bucket."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Change these to try this notebook out.\n",
    "BUCKET = 'your-project-id'\n",
    "PROJECT = 'your-project-id'\n",
    "REGION = 'us-central1'"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {},
   "outputs": [],
   "source": [
    "import os\n",
    "os.environ['BUCKET'] = BUCKET\n",
    "os.environ['PROJECT'] = PROJECT\n",
    "os.environ['REGION'] = REGION"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {},
   "outputs": [],
   "source": [
    "%%bash\n",
    "if ! gsutil ls | grep -q gs://${BUCKET}/; then\n",
    "    gsutil mb -l ${REGION} gs://${BUCKET}\n",
    "fi"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Import modules and prepare training data. (You use the [Boston Housing price regression dataset](https://keras.io/api/datasets/boston_housing/#load_data-function) as an example.)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {},
   "outputs": [],
   "source": [
    "import numpy as np\n",
    "from pandas import DataFrame\n",
    "\n",
    "import tensorflow as tf\n",
    "from tensorflow.keras import layers, models, datasets"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "metadata": {},
   "outputs": [],
   "source": [
    "(x_train, y_train), (x_test, y_test) = datasets.boston_housing.load_data(\n",
    "    path=\"boston_housing.npz\", test_split=0.2, seed=113\n",
    ")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/html": [
       "<div>\n",
       "<style scoped>\n",
       "    .dataframe tbody tr th:only-of-type {\n",
       "        vertical-align: middle;\n",
       "    }\n",
       "\n",
       "    .dataframe tbody tr th {\n",
       "        vertical-align: top;\n",
       "    }\n",
       "\n",
       "    .dataframe thead th {\n",
       "        text-align: right;\n",
       "    }\n",
       "</style>\n",
       "<table border=\"1\" class=\"dataframe\">\n",
       "  <thead>\n",
       "    <tr style=\"text-align: right;\">\n",
       "      <th></th>\n",
       "      <th>CRIM</th>\n",
       "      <th>ZN</th>\n",
       "      <th>INDUS</th>\n",
       "      <th>CHAS</th>\n",
       "      <th>NOX</th>\n",
       "      <th>RM</th>\n",
       "      <th>AGE</th>\n",
       "      <th>DIS</th>\n",
       "      <th>RAD</th>\n",
       "      <th>TAX</th>\n",
       "      <th>PTRATIO</th>\n",
       "      <th>B</th>\n",
       "      <th>LSTAT</th>\n",
       "    </tr>\n",
       "  </thead>\n",
       "  <tbody>\n",
       "    <tr>\n",
       "      <th>count</th>\n",
       "      <td>404.000000</td>\n",
       "      <td>404.000000</td>\n",
       "      <td>404.000000</td>\n",
       "      <td>404.000000</td>\n",
       "      <td>404.000000</td>\n",
       "      <td>404.000000</td>\n",
       "      <td>404.000000</td>\n",
       "      <td>404.000000</td>\n",
       "      <td>404.000000</td>\n",
       "      <td>404.000000</td>\n",
       "      <td>404.000000</td>\n",
       "      <td>404.000000</td>\n",
       "      <td>404.000000</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>mean</th>\n",
       "      <td>3.745111</td>\n",
       "      <td>11.480198</td>\n",
       "      <td>11.104431</td>\n",
       "      <td>0.061881</td>\n",
       "      <td>0.557356</td>\n",
       "      <td>6.267082</td>\n",
       "      <td>69.010644</td>\n",
       "      <td>3.740271</td>\n",
       "      <td>9.440594</td>\n",
       "      <td>405.898515</td>\n",
       "      <td>18.475990</td>\n",
       "      <td>354.783168</td>\n",
       "      <td>12.740817</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>std</th>\n",
       "      <td>9.240734</td>\n",
       "      <td>23.767711</td>\n",
       "      <td>6.811308</td>\n",
       "      <td>0.241238</td>\n",
       "      <td>0.117293</td>\n",
       "      <td>0.709788</td>\n",
       "      <td>27.940665</td>\n",
       "      <td>2.030215</td>\n",
       "      <td>8.698360</td>\n",
       "      <td>166.374543</td>\n",
       "      <td>2.200382</td>\n",
       "      <td>94.111148</td>\n",
       "      <td>7.254545</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>min</th>\n",
       "      <td>0.006320</td>\n",
       "      <td>0.000000</td>\n",
       "      <td>0.460000</td>\n",
       "      <td>0.000000</td>\n",
       "      <td>0.385000</td>\n",
       "      <td>3.561000</td>\n",
       "      <td>2.900000</td>\n",
       "      <td>1.129600</td>\n",
       "      <td>1.000000</td>\n",
       "      <td>188.000000</td>\n",
       "      <td>12.600000</td>\n",
       "      <td>0.320000</td>\n",
       "      <td>1.730000</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>25%</th>\n",
       "      <td>0.081437</td>\n",
       "      <td>0.000000</td>\n",
       "      <td>5.130000</td>\n",
       "      <td>0.000000</td>\n",
       "      <td>0.453000</td>\n",
       "      <td>5.874750</td>\n",
       "      <td>45.475000</td>\n",
       "      <td>2.077100</td>\n",
       "      <td>4.000000</td>\n",
       "      <td>279.000000</td>\n",
       "      <td>17.225000</td>\n",
       "      <td>374.672500</td>\n",
       "      <td>6.890000</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>50%</th>\n",
       "      <td>0.268880</td>\n",
       "      <td>0.000000</td>\n",
       "      <td>9.690000</td>\n",
       "      <td>0.000000</td>\n",
       "      <td>0.538000</td>\n",
       "      <td>6.198500</td>\n",
       "      <td>78.500000</td>\n",
       "      <td>3.142300</td>\n",
       "      <td>5.000000</td>\n",
       "      <td>330.000000</td>\n",
       "      <td>19.100000</td>\n",
       "      <td>391.250000</td>\n",
       "      <td>11.395000</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>75%</th>\n",
       "      <td>3.674808</td>\n",
       "      <td>12.500000</td>\n",
       "      <td>18.100000</td>\n",
       "      <td>0.000000</td>\n",
       "      <td>0.631000</td>\n",
       "      <td>6.609000</td>\n",
       "      <td>94.100000</td>\n",
       "      <td>5.118000</td>\n",
       "      <td>24.000000</td>\n",
       "      <td>666.000000</td>\n",
       "      <td>20.200000</td>\n",
       "      <td>396.157500</td>\n",
       "      <td>17.092500</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>max</th>\n",
       "      <td>88.976200</td>\n",
       "      <td>100.000000</td>\n",
       "      <td>27.740000</td>\n",
       "      <td>1.000000</td>\n",
       "      <td>0.871000</td>\n",
       "      <td>8.725000</td>\n",
       "      <td>100.000000</td>\n",
       "      <td>10.710300</td>\n",
       "      <td>24.000000</td>\n",
       "      <td>711.000000</td>\n",
       "      <td>22.000000</td>\n",
       "      <td>396.900000</td>\n",
       "      <td>37.970000</td>\n",
       "    </tr>\n",
       "  </tbody>\n",
       "</table>\n",
       "</div>"
      ],
      "text/plain": [
       "             CRIM          ZN       INDUS        CHAS         NOX          RM  \\\n",
       "count  404.000000  404.000000  404.000000  404.000000  404.000000  404.000000   \n",
       "mean     3.745111   11.480198   11.104431    0.061881    0.557356    6.267082   \n",
       "std      9.240734   23.767711    6.811308    0.241238    0.117293    0.709788   \n",
       "min      0.006320    0.000000    0.460000    0.000000    0.385000    3.561000   \n",
       "25%      0.081437    0.000000    5.130000    0.000000    0.453000    5.874750   \n",
       "50%      0.268880    0.000000    9.690000    0.000000    0.538000    6.198500   \n",
       "75%      3.674808   12.500000   18.100000    0.000000    0.631000    6.609000   \n",
       "max     88.976200  100.000000   27.740000    1.000000    0.871000    8.725000   \n",
       "\n",
       "              AGE         DIS         RAD         TAX     PTRATIO           B  \\\n",
       "count  404.000000  404.000000  404.000000  404.000000  404.000000  404.000000   \n",
       "mean    69.010644    3.740271    9.440594  405.898515   18.475990  354.783168   \n",
       "std     27.940665    2.030215    8.698360  166.374543    2.200382   94.111148   \n",
       "min      2.900000    1.129600    1.000000  188.000000   12.600000    0.320000   \n",
       "25%     45.475000    2.077100    4.000000  279.000000   17.225000  374.672500   \n",
       "50%     78.500000    3.142300    5.000000  330.000000   19.100000  391.250000   \n",
       "75%     94.100000    5.118000   24.000000  666.000000   20.200000  396.157500   \n",
       "max    100.000000   10.710300   24.000000  711.000000   22.000000  396.900000   \n",
       "\n",
       "            LSTAT  \n",
       "count  404.000000  \n",
       "mean    12.740817  \n",
       "std      7.254545  \n",
       "min      1.730000  \n",
       "25%      6.890000  \n",
       "50%     11.395000  \n",
       "75%     17.092500  \n",
       "max     37.970000  "
      ]
     },
     "execution_count": 6,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "df = DataFrame(x_train)\n",
    "df.columns = ['CRIM', 'ZN', 'INDUS', 'CHAS', 'NOX', 'RM', 'AGE', 'DIS', 'RAD', 'TAX', 'PTRATIO', 'B', 'LSTAT']\n",
    "df.describe()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Create a model to predict average prices and train it."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Model: \"sequential\"\n",
      "_________________________________________________________________\n",
      "Layer (type)                 Output Shape              Param #   \n",
      "=================================================================\n",
      "hidden (Dense)               (None, 8)                 112       \n",
      "_________________________________________________________________\n",
      "output (Dense)               (None, 1)                 9         \n",
      "=================================================================\n",
      "Total params: 121\n",
      "Trainable params: 121\n",
      "Non-trainable params: 0\n",
      "_________________________________________________________________\n"
     ]
    }
   ],
   "source": [
    "model = models.Sequential()\n",
    "model.add(layers.Input(shape=(13,), name='features'))\n",
    "model.add(layers.Dense(8, activation='relu', name='hidden'))\n",
    "model.add(layers.Dense(1, activation=None, name='output'))\n",
    "model.summary()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "metadata": {},
   "outputs": [],
   "source": [
    "model.compile(optimizer='adam', loss='mse')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Training will take a few minutes."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "metadata": {},
   "outputs": [],
   "source": [
    "history = model.fit(\n",
    "    x_train, y_train, validation_data=(x_test, y_test),\n",
    "    batch_size=64, epochs=1000, verbose=0)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "<AxesSubplot:>"
      ]
     },
     "execution_count": 10,
     "metadata": {},
     "output_type": "execute_result"
    },
    {
     "data": {
      "image/png": "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\n",
      "text/plain": [
       "<Figure size 432x288 with 1 Axes>"
      ]
     },
     "metadata": {
      "needs_background": "light"
     },
     "output_type": "display_data"
    },
    {
     "data": {
      "image/png": "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\n",
      "text/plain": [
       "<Figure size 432x288 with 1 Axes>"
      ]
     },
     "metadata": {
      "needs_background": "light"
     },
     "output_type": "display_data"
    }
   ],
   "source": [
    "DataFrame({'train_loss': history.history['loss'], \n",
    "           'test_loss': history.history['val_loss']}).plot()\n",
    "DataFrame({'train_rmse': np.sqrt(history.history['loss']), \n",
    "           'test_rmse': np.sqrt(history.history['val_loss'])}).plot(ylim=[0, 15])"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Create a wrapper model so that you can add an identifier key to each example."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Model: \"functional_1\"\n",
      "__________________________________________________________________________________________________\n",
      "Layer (type)                    Output Shape         Param #     Connected to                     \n",
      "==================================================================================================\n",
      "features (InputLayer)           [(None, 13)]         0                                            \n",
      "__________________________________________________________________________________________________\n",
      "key (InputLayer)                [(None,)]            0                                            \n",
      "__________________________________________________________________________________________________\n",
      "hidden (Dense)                  (None, 8)            112         features[0][0]                   \n",
      "__________________________________________________________________________________________________\n",
      "reshape (Reshape)               (None, 1)            0           key[0][0]                        \n",
      "__________________________________________________________________________________________________\n",
      "output (Dense)                  (None, 1)            9           hidden[0][0]                     \n",
      "__________________________________________________________________________________________________\n",
      "tf_op_layer_Cast (TensorFlowOpL [(None, 1)]          0           reshape[0][0]                    \n",
      "__________________________________________________________________________________________________\n",
      "prediction_with_key (Concatenat (None, 2)            0           output[0][0]                     \n",
      "                                                                 tf_op_layer_Cast[0][0]           \n",
      "==================================================================================================\n",
      "Total params: 121\n",
      "Trainable params: 121\n",
      "Non-trainable params: 0\n",
      "__________________________________________________________________________________________________\n"
     ]
    }
   ],
   "source": [
    "key = layers.Input(shape=(), name='key', dtype='int32')\n",
    "pred = layers.Concatenate(name='prediction_with_key')(\n",
    "    [model.output, tf.cast(layers.Reshape((1,))(key), tf.float32)])\n",
    "wrapper_model = models.Model(inputs=[model.input, key], outputs=pred)\n",
    "wrapper_model.compile()\n",
    "wrapper_model.summary()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 12,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "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\n",
      "text/plain": [
       "<IPython.core.display.Image object>"
      ]
     },
     "execution_count": 12,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "tf.keras.utils.plot_model(\n",
    "    model=wrapper_model, to_file=\"dnn_model.png\", show_shapes=False, rankdir=\"LR\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Use the predict method of the wrapper model."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 13,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "array([[ 7.9771166,  0.       ],\n",
       "       [21.49156  ,  1.       ],\n",
       "       [23.832067 ,  2.       ],\n",
       "       [29.42475  ,  3.       ],\n",
       "       [26.124998 ,  4.       ],\n",
       "       [20.89533  ,  5.       ],\n",
       "       [29.164335 ,  6.       ],\n",
       "       [26.358938 ,  7.       ],\n",
       "       [20.769209 ,  8.       ],\n",
       "       [20.851141 ,  9.       ]], dtype=float32)"
      ]
     },
     "execution_count": 13,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "wrapper_model.predict([x_test[:10], tf.constant(range(10))])"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Export the wrapper model in the saved_model format."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 14,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "WARNING:tensorflow:From /opt/conda/lib/python3.7/site-packages/tensorflow/python/training/tracking/tracking.py:111: Model.state_updates (from tensorflow.python.keras.engine.training) is deprecated and will be removed in a future version.\n",
      "Instructions for updating:\n",
      "This property should not be used in TensorFlow 2.0, as updates are applied automatically.\n",
      "WARNING:tensorflow:From /opt/conda/lib/python3.7/site-packages/tensorflow/python/training/tracking/tracking.py:111: Layer.updates (from tensorflow.python.keras.engine.base_layer) is deprecated and will be removed in a future version.\n",
      "Instructions for updating:\n",
      "This property should not be used in TensorFlow 2.0, as updates are applied automatically.\n",
      "INFO:tensorflow:Assets written to: ./export/assets\n"
     ]
    }
   ],
   "source": [
    "export_path = \"./export\"\n",
    "wrapper_model.save(export_path, save_format='tf')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 15,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "The given SavedModel SignatureDef contains the following input(s):\n",
      "  inputs['features'] tensor_info:\n",
      "      dtype: DT_FLOAT\n",
      "      shape: (-1, 13)\n",
      "      name: serving_default_features:0\n",
      "  inputs['key'] tensor_info:\n",
      "      dtype: DT_INT32\n",
      "      shape: (-1)\n",
      "      name: serving_default_key:0\n",
      "The given SavedModel SignatureDef contains the following output(s):\n",
      "  outputs['prediction_with_key'] tensor_info:\n",
      "      dtype: DT_FLOAT\n",
      "      shape: (-1, 2)\n",
      "      name: StatefulPartitionedCall:0\n",
      "Method name is: tensorflow/serving/predict\n"
     ]
    }
   ],
   "source": [
    "%%bash\n",
    "saved_model_cli show --dir \"./export\" --tag_set serve --signature_def serving_default"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Make local predictions with the exported model."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 16,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "{'prediction_with_key': <tf.Tensor: shape=(10, 2), dtype=float32, numpy=\n",
       " array([[ 7.9771166,  0.       ],\n",
       "        [21.49156  ,  1.       ],\n",
       "        [23.832067 ,  2.       ],\n",
       "        [29.42475  ,  3.       ],\n",
       "        [26.124998 ,  4.       ],\n",
       "        [20.89533  ,  5.       ],\n",
       "        [29.164335 ,  6.       ],\n",
       "        [26.358938 ,  7.       ],\n",
       "        [20.769209 ,  8.       ],\n",
       "        [20.851141 ,  9.       ]], dtype=float32)>}"
      ]
     },
     "execution_count": 16,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "serving_fn = models.load_model(export_path).signatures['serving_default']\n",
    "serving_fn(features=tf.constant(x_test[:10].tolist()), key=tf.constant(range(10)))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Deploy the exported model to AI Platform.\n",
    "\n",
    "**Note**: the latest runtime that supports batch prediction is 2.1\n",
    "\n",
    "https://cloud.google.com/ai-platform/training/docs/runtime-version-list"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 17,
   "metadata": {},
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "Using endpoint [https://ml.googleapis.com/]\n",
      "Created ml engine model [projects/your-project-id/models/housing_price1].\n",
      "Using endpoint [https://ml.googleapis.com/]\n",
      "Creating version (this might take a few minutes)......\n",
      "...............................................................................................................................................................................................................................................done.\n"
     ]
    }
   ],
   "source": [
    "%%bash\n",
    "MODEL_NAME=\"housing_price1\"\n",
    "MODEL_VERSION=\"v1\"\n",
    "MODEL_LOCATION=\"./export\"\n",
    "gcloud ai-platform models create ${MODEL_NAME} --regions $REGION\n",
    "gcloud ai-platform versions create ${MODEL_VERSION} --model ${MODEL_NAME} \\\n",
    "  --origin ${MODEL_LOCATION} --runtime-version 2.1 \\\n",
    "  --staging-bucket gs://$BUCKET --region global"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Make online predictions with the deployed model."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 19,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "{\n",
      "    \"predictions\": [\n",
      "        {\n",
      "            \"prediction_with_key\": [\n",
      "                7.977111339569092,\n",
      "                0.0\n",
      "            ]\n",
      "        },\n",
      "        {\n",
      "            \"prediction_with_key\": [\n",
      "                21.491554260253906,\n",
      "                1.0\n",
      "            ]\n",
      "        }\n",
      "    ]\n",
      "}\n"
     ]
    }
   ],
   "source": [
    "from googleapiclient import discovery\n",
    "from oauth2client.client import GoogleCredentials\n",
    "import json\n",
    "\n",
    "credentials = GoogleCredentials.get_application_default()\n",
    "api = discovery.build('ml', 'v1', credentials=credentials, cache_discovery=False)\n",
    "\n",
    "request_data =  {'instances':\n",
    "  [\n",
    "    {\"features\": [18.0846, 0, 18.1, 0, 0.679, 6.434, 100, 1.8347, 24, 666, 20.2, 27.25, 29.05], \"key\": 0},\n",
    "    {\"features\": [0.12329, 0, 10.01, 0, 0.547, 5.913, 92.9, 2.3534, 6.0, 432, 17.8, 394.95, 16.21], \"key\": 1},\n",
    "  ]\n",
    "}\n",
    "\n",
    "parent = 'projects/%s/models/%s/versions/%s' % (PROJECT, 'housing_price1', 'v1')\n",
    "response = api.projects().predict(body=request_data, name=parent).execute()\n",
    "print(json.dumps(response, sort_keys = True, indent = 4))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Create an input file and execute a batch prediction job."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 20,
   "metadata": {},
   "outputs": [],
   "source": [
    "with open('./input.json', 'w') as f:\n",
    "    for features, key in zip(x_test[:10].tolist(), np.arange(10).tolist()):\n",
    "        print(json.dumps({'features': features, 'key':key}), file=f)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 21,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "{\"features\": [18.0846, 0.0, 18.1, 0.0, 0.679, 6.434, 100.0, 1.8347, 24.0, 666.0, 20.2, 27.25, 29.05], \"key\": 0}\n",
      "{\"features\": [0.12329, 0.0, 10.01, 0.0, 0.547, 5.913, 92.9, 2.3534, 6.0, 432.0, 17.8, 394.95, 16.21], \"key\": 1}\n",
      "{\"features\": [0.05497, 0.0, 5.19, 0.0, 0.515, 5.985, 45.4, 4.8122, 5.0, 224.0, 20.2, 396.9, 9.74], \"key\": 2}\n",
      "{\"features\": [1.27346, 0.0, 19.58, 1.0, 0.605, 6.25, 92.6, 1.7984, 5.0, 403.0, 14.7, 338.92, 5.5], \"key\": 3}\n",
      "{\"features\": [0.07151, 0.0, 4.49, 0.0, 0.449, 6.121, 56.8, 3.7476, 3.0, 247.0, 18.5, 395.15, 8.44], \"key\": 4}\n",
      "{\"features\": [0.27957, 0.0, 9.69, 0.0, 0.585, 5.926, 42.6, 2.3817, 6.0, 391.0, 19.2, 396.9, 13.59], \"key\": 5}\n",
      "{\"features\": [0.03049, 55.0, 3.78, 0.0, 0.484, 6.874, 28.1, 6.4654, 5.0, 370.0, 17.6, 387.97, 4.61], \"key\": 6}\n",
      "{\"features\": [0.03551, 25.0, 4.86, 0.0, 0.426, 6.167, 46.7, 5.4007, 4.0, 281.0, 19.0, 390.64, 7.51], \"key\": 7}\n",
      "{\"features\": [0.09299, 0.0, 25.65, 0.0, 0.581, 5.961, 92.9, 2.0869, 2.0, 188.0, 19.1, 378.09, 17.93], \"key\": 8}\n",
      "{\"features\": [3.56868, 0.0, 18.1, 0.0, 0.58, 6.437, 75.0, 2.8965, 24.0, 666.0, 20.2, 393.37, 14.36], \"key\": 9}\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "Copying file://./input.json [Content-Type=application/json]...\n",
      "/ [1 files][  1.1 KiB/  1.1 KiB]                                                \n",
      "Operation completed over 1 objects/1.1 KiB.                                      \n"
     ]
    }
   ],
   "source": [
    "%%bash\n",
    "gsutil cp ./input.json gs://$BUCKET/input/input.json\n",
    "gsutil cat gs://$BUCKET/input/input.json"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 22,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "jobId: batch_pred_20210112112025\n",
      "state: QUEUED\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "Job [batch_pred_20210112112025] submitted successfully.\n",
      "Your job is still active. You may view the status of your job with the command\n",
      "\n",
      "  $ gcloud ai-platform jobs describe batch_pred_20210112112025\n",
      "\n",
      "or continue streaming the logs with the command\n",
      "\n",
      "  $ gcloud ai-platform jobs stream-logs batch_pred_20210112112025\n"
     ]
    }
   ],
   "source": [
    "%%bash\n",
    "MODEL_NAME=\"housing_price1\"\n",
    "MODEL_VERSION=\"v1\"\n",
    "JOB_NAME=\"batch_pred_$(date +'%Y%m%d%H%M%S')\"\n",
    "INPUT_PATHS=\"gs://$BUCKET/input/*\"\n",
    "OUTPUT_PATH=\"gs://$BUCKET/output\"\n",
    "DATA_FORMAT=\"text\" # JSON data format\n",
    "gcloud ai-platform jobs submit prediction $JOB_NAME \\\n",
    "    --model $MODEL_NAME \\\n",
    "    --input-paths $INPUT_PATHS \\\n",
    "    --output-path $OUTPUT_PATH \\\n",
    "    --region $REGION \\\n",
    "    --data-format $DATA_FORMAT"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Open [Cloud Console](https://console.cloud.google.com/ai-platform/jobs) and wait for the batch prediction job to finish. When it has successfully completed, the prediction results are stored in the storage bucket."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 23,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "{\"prediction_with_key\": [7.977108955383301, 0.0]}\n",
      "{\"prediction_with_key\": [21.491567611694336, 1.0]}\n",
      "{\"prediction_with_key\": [23.832067489624023, 2.0]}\n",
      "{\"prediction_with_key\": [29.424741744995117, 3.0]}\n",
      "{\"prediction_with_key\": [26.124998092651367, 4.0]}\n",
      "{\"prediction_with_key\": [20.89533042907715, 5.0]}\n",
      "{\"prediction_with_key\": [29.16432762145996, 6.0]}\n",
      "{\"prediction_with_key\": [26.358930587768555, 7.0]}\n",
      "{\"prediction_with_key\": [20.769208908081055, 8.0]}\n",
      "{\"prediction_with_key\": [20.85109519958496, 9.0]}\n"
     ]
    }
   ],
   "source": [
    "!gsutil cat gs://$BUCKET/output/prediction.results*"
   ]
  }
 ],
 "metadata": {
  "environment": {
   "name": "tf2-gpu.2-3.m61",
   "type": "gcloud",
   "uri": "gcr.io/deeplearning-platform-release/tf2-gpu.2-3:m61"
  },
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.7.9"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 4
}
